﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Athena.Functions;

namespace Athena.Functions
{
    // Declare a Expression exception type
    public class EExprException : System.ApplicationException
    {
        public EExprException(string message)
            : base(message)
        {
        }
    }

    // Declare a Expression exception type
    public class ECompileException : System.ApplicationException
    {
        public ECompileException(string message)
            : base(message)
        {
        }
    }

    // Declare a Expression exception type
    public class EUnknownIdentifier : System.ApplicationException
    {
        public EUnknownIdentifier(string message)
            : base(message)
        {
        }
    }


    public enum TAssignBehaviour { eReportNewSymbols, eInstallSymbols };

    public class TAlgExpression
    {
        private String FExpression;
        private TParser FParser;
        private TBinarySTree FSymbolTable;
        public TBinarySTree GetSymbolTable()
        {
            return FSymbolTable;
        }
        public TTreeNode[] GetAllSymbols()
        {
            TTreeNode[] allSymbols = FSymbolTable.GetAllSymbols();
            List<TTreeNode> nonResrvSymbols = new List<TTreeNode>();
            for (int i = 0; i < allSymbols.Length; ++i)
            {
                bool reswrd = false;
                for (int j = 0; j < ReservedSymbols.Length; ++j)
                {
                    if (ReservedSymbols[j].Equals(allSymbols[i].name))
                    {
                        reswrd = true;
                        break;
                    }
                }
                if (!reswrd)
                    nonResrvSymbols.Add(allSymbols[i]);
            }
            return nonResrvSymbols.ToArray();
        }

        private Stack st = new Stack();

        public TAlgExpression()
        {
            FParser = new TParser(st);
            FSymbolTable = new TBinarySTree();

            addBuiltInSymbols();
        }

        public TAssignBehaviour AssignBehaviour
        {
            get { return FParser.FBehaviour; }
            set { FParser.FBehaviour = value; }
        }

        public static string[] ReservedSymbols = { "PI", "Degrees", "Time", "time" };

        private void addBuiltInSymbols()
        {
            FSymbolTable.insert("PI", Math.PI);
            //FSymbolTable.insert("E", Math.E);
            FSymbolTable.insert("Degrees", Math.PI / 180);
            FSymbolTable.insert("Time", 0);
            FSymbolTable.insert("time", 0);
        }

        public void clearSymbols()
        {
            FSymbolTable.clear();
            addBuiltInSymbols();
        }


        public double eval(String str)
        {
            if (str != "")
            {
                FExpression = str;
                FParser.ParseExp(FExpression, FSymbolTable);
                return FParser.run(FParser.FProgram, FSymbolTable);
            }
            else return 0;
        }


        // Fast version of the above, less error checking code but much faster }
        public double eval()
        {

            if (FExpression != "")
            {
                if (FParser.FProgram == null)
                {

                    try
                    {
                        FParser.ParseExp(FExpression, FSymbolTable);
                    }
                    catch (ECompileException)
                    {
                        FExpression = "0";
                        throw;
                    }
                }
                return FParser.run(FParser.FProgram, FSymbolTable);
            }
            else
                throw new Exception("No expression to evaluate");
        }

        // Convert string expression into internal rpn format
        // Usage: e.Compile ('cos(1.2)'); Subsequent calls to e.eval would
        // evaluate cos(1.2) much quicker
        public void compile(string s)
        {
            if (s != "")
            {
                // Only compile if the new expression is different }
                if (FExpression != s)
                {

                    FExpression = s;
                    try
                    {
                        FParser.ParseExp(FExpression, FSymbolTable);
                    }
                    catch (ECompileException)
                    {
                        FExpression = "0";
                        throw;
                    }
                }
            }
            else
                throw new Exception("TAlgExpression Error: no expression to evaluate");
        }


        // Declare and initialise a string variable in the symbol table } {
        // Usage: e.setVar ('a', 1.2); e.SetVar ('Pi', 3.1415);
        // e.EvalThis ('a*Pi');

        public TTreeNode setVar(string s, double d)
        {

            TTreeNode np = FSymbolTable.findSymbol(s);
            if (np != null)
                np.value = d;
            else np = FSymbolTable.insert(s, d);
            return np;
        }

        public TTreeNode setVarEx(string s, double d)
        {

            TTreeNode np = FSymbolTable.findSymbol(s);
            if (np != null)
            {
                np.value = d;
                return np;
            }
            return null;
        }

        //        public TTreeNode setVarEx (string s, double d) {
        //
        //            TTreeNode np = FSymbolTable.findSymbol (s);
        //            if (np != null) { 
        //                FSymbolTable.setValue (np, d); 
        //                return np;
        //            }
        //            else return null;
        //        } 

        // Fetch the value of a declared variable from the symbol table } {
        // Usage: e.setVar ('a', 1.2); x := e.getVar ('a'). x then equals 1.2 }
        public double getVar(string s)
        {
            TTreeNode np = FSymbolTable.findSymbol(s);
            if (np != null)
                return np.value;
            else
                throw new EUnknownIdentifier("Symbol <" + s + "> not declared");
        }

        public TTreeNode getSymbolHandle(string s)
        {
            TTreeNode np = FSymbolTable.findSymbol(s);
            if (np == null)
                throw new EExprException("Unable to locate symbol: " + s);
            else return np;
        }


        public void setValue(TTreeNode symbol, double value)
        {
            symbol.value = value;
        }

        // -------------------------------------------------------------------------
        // -------              PARSER ROUTINES FOLLOW                              
        // -------------------------------------------------------------------------


        // Virtual machine runtime codes
        public enum rpCodes : int
        {
            rpConstant, rpInst, rpUMinus, rpAdd, rpSub, rpMult, rpDiv, rpNot, rpAnd, rpOr,
            rpPower, rpSymbol, rpStop, rpAssign, rpLT, rpGT, rpLE, rpGE, rpNEQ,
            rpSin, rpCos, rpTan, rpError, rpFunction
        }


        public delegate double builtInFunction();

        public class functionType
        {

            public string name;
            public builtInFunction func;

            public functionType(string name, builtInFunction func)
            {
                this.name = name;
                this.func = func;
            }
        }



        public class functionList
        {

            ArrayList fList;
            Stack st;        // reference

            public double MySin()
            {
                return Math.Sin((double)st.Pop());
            }

            public double MyCos()
            {
                return Math.Cos((double)st.Pop());
            }

            public double MyTan()
            {
                return Math.Tan((double)st.Pop());
            }

            public double MyExp()
            {
                return Math.Exp((double)st.Pop());
            }

            public double MyLog()
            {
                return Math.Log10((double)st.Pop());
            }

            public double MyLn()
            {
                return Math.Log((double)st.Pop());
            }

            public double MySqr()
            {
                double x = (double)st.Pop();
                return x * x;
            }

            public double MySqrt()
            {
                return Math.Sqrt((double)st.Pop());
            }

            public double MyPower()
            {
                double p1 = (double)st.Pop();
                double p2 = (double)st.Pop();
                return Math.Pow(p2, p1);
            }

            public double MyGT()
            {
                double y = (double)st.Pop(),
                            x = (double)st.Pop();
                if (x > y)
                    return (1.0);
                else return (0.0);
                //return (sigmoid(10 * (x - y)));
            }

            public double MyGE()
            {
                double y = (double)st.Pop(),
                            x = (double)st.Pop();
                if (x >= y)
                    return (1.0);
                else return (0.0);
                //return (sigmoid(10 * (x - y)));
            }

            public double MyLT()
            {
                double y = (double)st.Pop(),
                            x = (double)st.Pop();
                if (x < y)
                    return (1.0);
                else return (0.0);
                //return (sigmoid(10 * (y - y)));
            }

            public double MyLE()
            {
                double y = (double)st.Pop(),
                            x = (double)st.Pop();
                if (x <= y)
                    return (1.0);
                else return (0.0);
                //return (sigmoid(10 * (y - y)));
            }

            public functionList(Stack st)
            {
                this.st = st;
                fList = new ArrayList();
                fList.Add(new functionType("sin", new builtInFunction(MySin)));
                fList.Add(new functionType("cos", new builtInFunction(MyCos)));
                fList.Add(new functionType("tan", new builtInFunction(MyTan)));
                fList.Add(new functionType("exp", new builtInFunction(MyExp)));
                fList.Add(new functionType("log10", new builtInFunction(MyLog)));
                fList.Add(new functionType("log", new builtInFunction(MyLn)));
                fList.Add(new functionType("sqr", new builtInFunction(MySqr)));
                fList.Add(new functionType("sqrt", new builtInFunction(MySqrt)));
                fList.Add(new functionType("pow", new builtInFunction(MyPower)));
                fList.Add(new functionType("gt", new builtInFunction(MyGT)));
                fList.Add(new functionType("lt", new builtInFunction(MyLT)));
                fList.Add(new functionType("ge", new builtInFunction(MyGE)));
                fList.Add(new functionType("le", new builtInFunction(MyLE)));
            }

            public int find(string name)
            {
                for (int i = 0; i < fList.Count; i++)
                    if (((functionType)fList[i]).name == name)
                        return i;

                return -1;
            }

            public functionType this[int index]
            {
                get { return (functionType)fList[index]; }
            }
        }

        // A single instruction code has the following structure
        public struct TRpnElement
        {
            public rpCodes opcode;
            public TTreeNode np;
            public builtInFunction func;
            public double constant;

            public TRpnElement(rpCodes opcode, TTreeNode np, double constant, builtInFunction func)
            {
                this.opcode = opcode;
                this.np = np;
                this.constant = constant;
                this.func = func;
            }
        }


        public class TParser
        {

            private TScanner sc;
            private TBinarySTree FSymTab;
            private String FExpression;
            public ArrayList FProgram;
            public TAssignBehaviour FBehaviour;
            private functionList fl;
            private Stack st;  // reference

            public TParser(Stack st)
            {
                sc = new TScanner();
                fl = new functionList(st);
                this.st = st;
            }

            void StoreConstant(double Value)
            {
                FProgram.Add(new TRpnElement(rpCodes.rpConstant, null, Value, null));
            }

            void StoreInst(rpCodes Value)
            {
                FProgram.Add(new TRpnElement(Value, null, 0, null));
            }

            void StoreInst(rpCodes Value, TTreeNode np)
            {
                FProgram.Add(new TRpnElement(Value, np, 0, null));
            }

            void StoreInst(rpCodes Value, builtInFunction func)
            {
                FProgram.Add(new TRpnElement(Value, null, 0, func));
            }

            void StoreSymbol(TTreeNode np)
            {
                FProgram.Add(new TRpnElement(rpCodes.rpSymbol, np, 0, null));
            }


            void factor()
            {
                TTreeNode np = null;

                switch (sc.token)
                {

                    case code.tDoubleToken:
                    case code.tIntToken:
                        StoreConstant(sc.tokenScalar);
                        sc.nextToken();
                        break;

                    case code.tWordToken:
                        // Check to see whether the name is a function identifier
                        int index = fl.find(sc.tokenString); // make it case insensitive
                        if (index != -1)
                        {
                            sc.nextToken();
                            if (sc.token != code.tLParenToken)
                            {
                                FProgram.Clear();
                                throw new EExprException("Syntax error: missing left bracket");
                            }
                            sc.nextToken();
                            doSimpleExpression();
                            while (sc.token == code.tCommaToken)
                            {
                                sc.nextToken();
                                doSimpleExpression();
                            }

                            if (sc.token != code.tRParenToken)
                            {
                                FProgram.Clear();
                                throw new EExprException("Syntax error: missing right bracket");
                            }
                            // enter function id into instruction stream, not an Instruction as it appears
                            StoreInst(rpCodes.rpFunction, fl[index].func);
                        }
                        else
                        {
                            // identifier wasn't a function name, so check in symbol table...
                            // Search the symbol table. Nil if the name cannot be found
                            np = FSymTab.findSymbol(sc.tokenString);
                            if (np == null)
                            {
                                // Check behaviour setting
                                if (FBehaviour == TAssignBehaviour.eInstallSymbols)
                                {
                                    // i.e user wishes to accept undeclared identifiers, 
                                    // add to symbol table, initialise to 0.0
                                    np = FSymTab.insert(sc.tokenString, 0.0);
                                    // add operand to rpn expression
                                    StoreSymbol(np);

                                }
                                else
                                {
                                    // User requests reporting of undeclared lefthand variables as an error
                                    FProgram.Clear();
                                    throw new EExprException("Syntax error: Unrecognised identifier " + sc.tokenString + " in expression");
                                }
                            }
                            else StoreSymbol(np);
                        }
                        sc.nextToken();
                        break;


                    case code.tLParenToken:
                        sc.nextToken();
                        doExpression();
                        if (sc.token == code.tRParenToken)
                            sc.nextToken();
                        else
                        {
                            FProgram.Clear();
                            throw new EExprException("Syntax error: missing right bracket");
                        }
                        break;

                    case code.tNotToken:
                        sc.nextToken();
                        factor();
                        StoreInst(rpCodes.rpNot);
                        break;

                    default:
                        FProgram.Clear();
                        throw new EExprException("Syntax error: expecting number, identifier or left bracket");
                        //break;

                }
            }


            // Deal with power operator
            void superTerm()
            {
                factor();
                while (sc.token == code.tPowerToken)
                {
                    sc.nextToken();
                    factor();
                    StoreInst(rpCodes.rpPower);
                }
            }


            // Deal with mult/div and 'and' operators
            void term()
            {
                code op = code.tEmptyToken;

                superTerm();
                while ((sc.token == code.tMultToken) || (sc.token == code.tDivToken) || (sc.token == code.tAndToken))
                {
                    op = sc.token;
                    sc.nextToken();
                    superTerm();

                    switch (op)
                    {
                        case code.tMultToken: StoreInst(rpCodes.rpMult); break;
                        case code.tDivToken: StoreInst(rpCodes.rpDiv); break;
                        case code.tAndToken: StoreInst(rpCodes.rpAnd); break;
                    }
                }
            }


            //  Parse things like sym + sym or sym - sym, checking also fo runary operators }
            void doSimpleExpression()
            {

                code op, unary_op;
                // remember unary + or - if there is one }
                unary_op = code.tPlusToken;
                if ((sc.token == code.tPlusToken) || (sc.token == code.tMinusToken))
                {
                    unary_op = sc.token;
                    sc.nextToken();
                }

                term();
                if (unary_op == code.tMinusToken) StoreInst(rpCodes.rpUMinus);

                while ((sc.token == code.tPlusToken) ||
                      (sc.token == code.tMinusToken) ||
                      (sc.token == code.tOrToken))
                {
                    op = sc.token;  // remember the token
                    sc.nextToken();
                    term();

                    switch (op)
                    {
                        case code.tPlusToken: StoreInst(rpCodes.rpAdd); break;
                        case code.tMinusToken: StoreInst(rpCodes.rpSub); break;
                        case code.tOrToken: StoreInst(rpCodes.rpOr); break;
                    }
                }
            }


            // Scan for boolean operators            
            public void doExpression()
            {

                code op;

                doSimpleExpression();

                while ((sc.token == code.tLessThanToken) ||
                       (sc.token == code.tLessThanOrEqualToken) ||
                       (sc.token == code.tMoreThanToken) ||
                       (sc.token == code.tMoreThanOrEqualToken) ||
                       (sc.token == code.tNotEqualToken) ||
                       (sc.token == code.tEqualsToken))
                {

                    op = sc.token;    // remember token
                    sc.nextToken();
                    doSimpleExpression();
                    switch (op)
                    {
                        case code.tLessThanToken: StoreInst(rpCodes.rpLT); break;
                        case code.tMoreThanToken: StoreInst(rpCodes.rpGT); break;
                        case code.tNotEqualToken: StoreInst(rpCodes.rpNEQ); break;
                        case code.tMoreThanOrEqualToken: StoreInst(rpCodes.rpGE); break;
                        case code.tLessThanOrEqualToken: StoreInst(rpCodes.rpLE); break;
                    }
                }
            }


            public void doAssignment(string name)
            {
                // Search for identifier 'y', Nil if not found
                TTreeNode np = FSymTab.findSymbol(name);
                if (np == null)
                {
                    // Check behaviour setting 
                    if (FBehaviour == TAssignBehaviour.eInstallSymbols)
                    {
                        // i.e user wishes to accept undeclared assigned identifiers, add to symbol table, init to 0.0
                        np = FSymTab.insert(name, 0.0);
                    }
                    else
                    {
                        // User requests reporting of undeclared lefthand variables as an error
                        FProgram.Clear();
                        throw new EExprException("Syntax error: Unrecognised identifier <" + name + "> on left of expression");
                    }
                }

                sc.nextToken();
                sc.nextToken();
                doExpression();
                StoreInst(rpCodes.rpAssign, np);
            }


            public void doStatement()
            {
                String savedName;
                if (sc.token == code.tWordToken)
                {
                    savedName = sc.tokenString;

                    //sc.nextToken();    // look ahead for '=' token
                    //if (sc.token == code.tEqualsToken) 
                    sc.skipBlanks();
                    if (sc.fch == '=')
                        doAssignment(savedName);
                    else
                    {
                        // Not an assignment, therefore Unwind back to start before processing expression}
                        //sc.UnGetToken();
                        doExpression();
                    }
                }
                else
                    doExpression();
            }


            // compiles an expression into an internal static array called rpn_array
            public void ParseExp(String expr, TBinarySTree SymTab)
            {

                // make a local copy of the symbol table pointer so that
                // it it accessible throughout the unit
                FSymTab = SymTab;
                FExpression = expr;
                sc.stream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(expr));

                // Create space for the rpn expression }
                if (FProgram == null) FProgram = new ArrayList(); else FProgram.Clear();

                // start scan by fetching first character
                sc.startScanner();
                sc.nextToken();
                doStatement();   // Note statments can be spearated by ';'
                while ((sc.token == code.tSemiColonToken) && (sc.token != code.tEndToken))
                {
                    sc.nextToken();
                    if (sc.token == code.tEndToken) break;
                    doStatement();
                }

                // At this point we should have reached end of string, if we havn't
                // then it's a syntax error }
                //if sc.token <> End_of_String then
                //   begin
                //   FreeProgram;
                //   raise ESyntaxError.Create ('Syntax error: reached end of equation too soon, something missing?');
                //   end; //...and don't forget the terminator instruction
                StoreInst(rpCodes.rpStop);
            }


            private static double sigmoid(double x)
            {
                return 1 / (1 + Math.Exp(-x));
            }

            // -------------------------------------------------------------------------
            // Evaluator Method
            // -------------------------------------------------------------------------

            public double run(ArrayList p, TBinarySTree bt)
            {

                int i = 0;
                while (true)
                {
                    TRpnElement rp = (TRpnElement)p[i];
                    switch (rp.opcode)
                    {

                        case rpCodes.rpSymbol:
                            st.Push(rp.np.value);
                            break;

                        case rpCodes.rpUMinus:
                            st.Push(-(double)st.Pop());
                            break;

                        case rpCodes.rpAdd:
                            st.Push((double)st.Pop() + (double)st.Pop());
                            break;

                        case rpCodes.rpSub:
                            double x1 = (double)st.Pop();
                            double x2 = (double)st.Pop();
                            st.Push(x2 - x1);
                            break;

                        case rpCodes.rpMult:
                            st.Push((double)st.Pop() * (double)st.Pop());
                            break;

                        case rpCodes.rpDiv:
                            double x = (double)st.Pop();
                            double y = (double)st.Pop();
                            st.Push(y / x);
                            break;

                        case rpCodes.rpPower:
                            x = (double)st.Pop();
                            y = (double)st.Pop();
                            st.Push(Math.Pow(y, x));
                            break;

                        case rpCodes.rpConstant:
                            st.Push(rp.constant);
                            break;

                        case rpCodes.rpFunction:
                            st.Push(rp.func());
                            break;

                        case rpCodes.rpAssign:
                            rp.np.value = (double)st.Pop();
                            break;


                        case rpCodes.rpAnd:
                            x = (double)st.Pop();
                            y = (double)st.Pop();
                            if ((x == 1) && (y == 1))
                                 st.Push(1.0);
                             else st.Push(0.0);
                            //st.Push(sigmoid(10 * (x + y - 2)));
                            break;

                        case rpCodes.rpOr:
                            x = (double)st.Pop();
                            y = (double)st.Pop();
                            if ((x == 1) || (y == 1))
                                st.Push(1.0);
                            else st.Push(0.0);
                            //st.Push(sigmoid(10 * (x + y - 1)));
                            break;

                        case rpCodes.rpNot:
                            x = (double)st.Pop();
                            if (x == 1)
                                st.Push(0.0);
                            else st.Push(1.0);
                            //st.Push(sigmoid(10 * (-x)));
                            break;

                        case rpCodes.rpGT:
                            y = (double)st.Pop();
                            x = (double)st.Pop();
                            if (x > y)
                                st.Push(1.0);
                            else st.Push(0.0);
                            //st.Push(sigmoid(10 * (x - y)));
                            break;

                        case rpCodes.rpGE:
                            y = (double)st.Pop();
                            x = (double)st.Pop();
                            if (x >= y)
                                st.Push(1.0);
                            else st.Push(0.0);
                            //st.Push(sigmoid(10 * (x - y)));
                            break;

                        case rpCodes.rpLT:
                            y = (double)st.Pop();
                            x = (double)st.Pop();
                            if (x < y)
                                st.Push(1.0);
                            else st.Push(0.0);
                            //st.Push(sigmoid(10 * (y - x)));
                            break;

                        case rpCodes.rpLE:
                            y = (double)st.Pop();
                            x = (double)st.Pop();
                            if (x <= y)
                                st.Push(1.0);
                            else st.Push(0.0);
                            //st.Push(sigmoid(10 * (y - x)));
                            break;

                        case rpCodes.rpStop:
                            if (st.Count > 0)
                                return (double)st.Pop();
                            else return 0.0;
                            //break;
                    }
                    i++;
                }
            }
        }

    }
}
